[harekazectf2019]easyNotes 题目来源:BUUCTF
题目类型:WEB
设计考点:Session伪造,Session反序列化
这是一个note服务网页,登录后可以添加note包括标题和内容,然后可以打包成ZIP或者TAR文件进行下载,题目提供了源码首先进行代码审计
1 2 3 4 5 6 7 8 9 10 11 12 <section > <h2 > Get flag</h2 > <p > <?php if (is_admin ()) { echo "Congratulations! The flag is: <code>" . getenv ('FLAG' ) . "</code>" ; } else { echo "You are not an admin :(" ; } ?> </p > </section >
通过审计该代码,我们发现当is_admin函数返回值为true时即可得到flag,我们跟进is_admin函数
1 2 3 4 5 6 7 8 function is_admin ( ) { if (!isset ($_SESSION ['admin' ])) { return false ; } return $_SESSION ['admin' ] === true ; } 发现他是获取的SESSION文件中的admin值 那么思路就是怎么伪造session
1 2 3 <?php define ('TEMP_DIR' , '/var/www/tmp' );
这个文件标识了临时文件存储路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 <?php function redirect ($path ) { header ('Location: ' . $path ); exit (); }function e ($str ) { return htmlspecialchars ($str , ENT_QUOTES); }function validate_user ($user ) { if (!is_string ($user )) { return false ; } return preg_match ('/\A[0-9A-Z_-]{4,64}\z/i' , $user ); }function is_logged_in ( ) { return isset ($_SESSION ['user' ]) && !empty ($_SESSION ['user' ]); }function set_user ($user ) { $_SESSION ['user' ] = $user ; }function get_user ( ) { return $_SESSION ['user' ]; }function is_admin ( ) { if (!isset ($_SESSION ['admin' ])) { return false ; } return $_SESSION ['admin' ] === true ; }function get_notes ( ) { if (!isset ($_SESSION ['notes' ])) { $_SESSION ['notes' ] = []; } return $_SESSION ['notes' ]; }function add_note ($title , $body ) { $notes = get_notes (); array_push ($notes , [ 'title' => $title , 'body' => $body , 'id' => hash ('sha256' , microtime ()) ]); $_SESSION ['notes' ] = $notes ; }function find_note ($notes , $id ) { for ($index = 0 ; $index < count ($notes ); $index ++) { if ($notes [$index ]['id' ] === $id ) { return $index ; } } return FALSE ; }function delete_note ($id ) { $notes = get_notes (); $index = find_note ($notes , $id ); if ($index !== FALSE ) { array_splice ($notes , $index , 1 ); } $_SESSION ['notes' ] = $notes ; }<?php error_reporting (0 );require_once ('config.php' );require_once ('lib.php' );session_save_path (TEMP_DIR);session_start ();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <?php require_once ('init.php' );if (!is_logged_in ()) { redirect ('/?page=home' ); }$notes = get_notes ();if (!isset ($_GET ['type' ]) || empty ($_GET ['type' ])) { $type = 'zip' ; } else { $type = $_GET ['type' ]; }$filename = get_user () . '-' . bin2hex (random_bytes (8 )) . '.' . $type ;$filename = str_replace ('..' , '' , $filename ); $path = TEMP_DIR . '/' . $filename ;if ($type === 'tar' ) { $archive = new PharData ($path ); $archive ->startBuffering (); } else { $archive = new ZipArchive (); $archive ->open ($path , ZIPARCHIVE::CREATE | ZipArchive ::OVERWRITE ); }for ($index = 0 ; $index < count ($notes ); $index ++) { $note = $notes [$index ]; $title = $note ['title' ]; $title = preg_replace ('/[^!-~]/' , '-' , $title ); $title = preg_replace ('#[/\\?*.]#' , '-' , $title ); $archive ->addFromString ("{$index} _{$title} .json" , json_encode ($note )); }if ($type === 'tar' ) { $archive ->stopBuffering (); } else { $archive ->close (); }header ('Content-Disposition: attachment; filename="' . $filename . '";' );header ('Content-Length: ' . filesize ($path ));header ('Content-Type: application/zip' );readfile ($path );
可以发现session文件保存路径与note文件导出的路径一样,看看文件名的命名方式
1 2 3 $file name = get_user() . '-' . bin2hex(random_bytes(8 )) . '.' . $type ;$file name = str_replace('..' , '' , $file name); $pat h = TEMP_DIR . '/' . $file name;
通过get_user函数获取用户名然后用一段随机字符串加上文件类型,这里的get_user内容可控,登录时修改用户名即可,但是他还有type拼接且type无法为空不然就会被设置为ZIP,第二行代码中将..替换为空为了防止目录穿越,那么我们就可以让type的值为.这样filename中就有..被替换为空那么后缀名就消失了,我们成功构造出了一个符合要求的session文件名,看看内容是否可控
1 2 3 4 5 6 7 for ($index = 0 ; $index < count($notes); $index++) { $note = $notes[$index]; $title = $note['title' ]; $title = preg_replace('/[^!-~]/' , '-' , $title); $title = preg_replace('#[/\\?*.]#' , '-' , $title); // delete suspicious characters $archive->addFromString("{$index}_{$title}.json" , json_encode($note)); }
内容是可控的我们新建note的title值会被写入到文件当中,那么思路就结束了
1 2 3 4 5 1、登录用户名设置为sess_ 2、新建note,title值为|N;admin|b:1; (body任意) 3、export 文件,BP拦截将type 值设置为. 4、将文件名中SESS_后的字符串记录 5、打开flag获取页面设置session值为第四步的字符串,这样他就会自动反序列化/var/www/tmp中我们植入伪造admin的文件,这样$_session ['admin' ]=true 从而获取flag